/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "MiniDump.h"

#ifdef GENERATE_MINIDUMPS

#include <windows.h>
#include <ShlObj.h>
#include <Dbghelp.h>
#include <time.h>
#include <Converter.h>

namespace liba
{

static wchar_t sz_crash_report[MAX_PATH] = L"minidump.dmp";
static wchar_t sz_crash_report_full[MAX_PATH] = L"fulldump.dmp";
static HINSTANCE hDbgHelp;

typedef BOOL (WINAPI *t_MINIDUMPWRITEDUMP)(
	IN HANDLE hProcess,
	IN DWORD ProcessId,
	IN HANDLE hFile,
	IN MINIDUMP_TYPE DumpType,
	IN CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, OPTIONAL
	IN CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, OPTIONAL
	IN CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam OPTIONAL
	);

static t_MINIDUMPWRITEDUMP minidump = 0;

static bool get_special_folder(int shell_folder_id, wchar_t * buffer, int size)
{
	LPITEMIDLIST list;
	if(S_OK != SHGetSpecialFolderLocation(0, shell_folder_id, &list))
		return false;

	//in docs regarding the SHGetPathFromIDList() function, there is no way to determine the size of the buffer
	if(!SHGetPathFromIDListW(list, buffer))
		return false;

	return true;
}

static void get_app_name(wchar_t * buffer, size_t size)
{
	wchar_t module_filename[4096];
	size_t i = GetModuleFileNameW(0, module_filename, 4096);
	size_t length = i;

	while(module_filename[i--] != L'\\' && i >= 0)
	{}

	i += 2;	//skip slash and advance to next symbol
	while(i < length && i < size-1)
	{
		*buffer++ = module_filename[i++];
	}

	*buffer = 0;
};

LONG WINAPI UnhandledExceptionFilter(_EXCEPTION_POINTERS* ExceptionInfo)
{
	MINIDUMP_EXCEPTION_INFORMATION ExpParam = {};
	ExpParam.ThreadId = GetCurrentThreadId();
	ExpParam.ExceptionPointers = ExceptionInfo;
	ExpParam.ClientPointers = TRUE;

	// do a mini dump    
	HANDLE hDmp = ::CreateFileW(sz_crash_report, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, NULL);
	minidump(::GetCurrentProcess(), ::GetCurrentProcessId(), hDmp, MiniDumpNormal, &ExpParam, NULL, NULL);

	::CloseHandle(hDmp);

	// we can do a full dump too, and also choose to terminate without windows error dialog coming up
//	if (false)	// disabled for now
	{
		int res = ::MessageBoxW(NULL, L"Whoops! There's been an error. Do you want to generate a complete memory dump?", L"Fatal Error", MB_YESNO | MB_ICONERROR);
		if (res == IDYES)
		{
			HANDLE hDmp = ::CreateFileW(sz_crash_report_full, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, NULL);
			minidump(::GetCurrentProcess(), ::GetCurrentProcessId(), hDmp, MiniDumpWithFullMemory, &ExpParam, NULL, NULL);
			::CloseHandle(hDmp);
		}

		TerminateProcess(::GetCurrentProcess(), -1);
	}

	return EXCEPTION_CONTINUE_SEARCH;
}

void init_dump(const std::string & caption)
{
	hDbgHelp = LoadLibraryA("dbghelp.dll");  
	if(hDbgHelp)
	{
		minidump = (t_MINIDUMPWRITEDUMP)GetProcAddress(hDbgHelp,"MiniDumpWriteDump");
		if(minidump)
		{
			SetUnhandledExceptionFilter(UnhandledExceptionFilter);    // This is the only thing that needs to be added, and it is process-wide (all threads, all modules)

			wchar_t buffer[4096];
			get_special_folder(CSIDL_APPDATA, buffer, 4096);

			std::wstring wcaption;
			converter::convert( caption, &wcaption );

			wcscat(buffer, L"/");
			wcscat(buffer, wcaption.c_str());
			wcscat(buffer, L"/");

			wcscpy(sz_crash_report, buffer);
			wcscpy(sz_crash_report_full, buffer);

			wcscat(sz_crash_report, L"minidump");
			wcscat(sz_crash_report_full, L"fulldump");

			if (true)
			{
				wchar_t path[MAX_PATH] = {};
				time_t t = time(NULL);
				struct tm _tm = *gmtime(&t);
				wcsftime(path, MAX_PATH, L"-%y%m%d-%H%M%S.dmp", &_tm);
				wcscat(sz_crash_report, path);
				wcscat(sz_crash_report_full, path);
			}
			else
			{
				wcscat(sz_crash_report, L".dmp");
				wcscat(sz_crash_report_full, L".dmp");
			}
		}
	}
}

void done_dump()
{
	if(hDbgHelp)
	{
		FreeLibrary(hDbgHelp);
		hDbgHelp = 0;
		minidump = 0;
	}
};

} //namespace liba

#endif

